/* Copyright (C) 2003, Charles Wang.
 * Author:  Charles Wang <charles@linux.net.cn>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANT; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public LIcense for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include  <sys/types.h>
#include  <sys/stat.h>
#include  <dirent.h>
#include  <errno.h>
#include  <fcntl.h>
#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#include  <unistd.h>
#include  "newtutils.h"
#include  "mblog.h"

/* in magicbooter.c */
void open_shell(void);

#if !defined(NONEWT)
static void str2ip(ipinput *ii, const char *defvalue) {
    char  *ip, *ptr, *temp;

    ip = strdupa(defvalue);
    temp = strtok_r(ip, ".", &ptr);
    ii->v0 = (temp && strlen(temp) <= 3 && strspn(temp, "0123456789") == strlen(temp))
	? atoi(temp) : -1;
    if (ii->v0 < 0)  ii->v1 = -1;
    else {
	temp = strtok_r(NULL, ".", &ptr);
	ii->v1 = (temp && strlen(temp) <= 3 && strspn(temp, "0123456789") == strlen(temp))
	    ? atoi(temp) : -1;
    }
    if (ii->v1 < 0)  ii->v2 = -1;
    else {
	temp = strtok_r(NULL, ".", &ptr);
	ii->v2 = (temp && strlen(temp) <= 3 && strspn(temp, "0123456789") == strlen(temp))
	    ? atoi(temp) : -1;
    }
    if (ii->v2 < 0)  ii->v3 = -1;
    else {
	temp = strtok_r(NULL, ".", &ptr);
	ii->v3 = (temp && strlen(temp) <= 3 && strspn(temp, "0123456789") == strlen(temp))
	    ? atoi(temp) : -1;
    }
}
static void ip2str(ipinput *ii, char *ipbuf) {
    if (ii->v0 < 0 || ii->v1 < 0 || ii->v2 < 0 || ii->v3 < 0)
	*ipbuf = 0;
    else
	sprintf(ipbuf, "%d.%d.%d.%d", ii->v0, ii->v1, ii->v2, ii->v3);
}

void  ipinput_create(ipinput *ii, const char *title, const char *defvalue) {
    char           ipbuf[20];
    newtComponent  label;

    str2ip(ii, defvalue);

    /* Construct the interface. */
    ii->grid = newtCreateGrid(3, 1);
    label = newtLabel(-1, -1, title);
    newtGridSetField(ii->grid, 0, 0, NEWT_GRID_COMPONENT, label, 0, 0, 0, 0, 0, 0);
    label = newtLabel(-1, -1, ":");
    newtGridSetField(ii->grid, 1, 0, NEWT_GRID_COMPONENT, label, 1, 0, 1, 0, 0, 0);
    ip2str(ii, ipbuf);
    ii->entry = newtEntry(-1, -1, ipbuf, 16, (char **)NULL, 0);
    newtGridSetField(ii->grid, 2, 0, NEWT_GRID_COMPONENT, ii->entry, 0, 0, 0, 0, 0, 0);
}
void  ipinput_get_ip(ipinput *ii, char *result) {
    str2ip(ii, newtEntryGetValue(ii->entry));
    if (ii->v0 < 0 || ii->v1 < 0 || ii->v2 < 0 || ii->v3 < 0)
	sprintf(result, "INVALID");
    else
	sprintf(result, "%d.%d.%d.%d", ii->v0, ii->v1, ii->v2, ii->v3);
}
#endif

#define  SCALEWIDTH   50
ssize_t  newt_copy_file(int readfd, int writefd, ssize_t len,
		   const char *title, const char *filename) {
    char            labelbuf[128];
#if !defined(NONEWT)
    newtComponent   label, scale;
#endif
    char          * loadbuffer;  ssize_t  lbsize;
    ssize_t         rl, wl, bytes_copied;

    lbsize = (ssize_t)(len / SCALEWIDTH);
    if (lbsize < 16384)  lbsize = 16384;
    while (lbsize >= 16384) {
	loadbuffer = malloc(lbsize);
	if (loadbuffer != NULL)  break;
	lbsize = lbsize / 2;
    }

    if (loadbuffer == NULL) {
	newtWinMessage("Error", "OK", "Not enough memory to copy %s.", filename);
	return  0;
    }

    newtCenteredWindow(SCALEWIDTH + 2, 5, title);
    snprintf(labelbuf, sizeof(labelbuf), "Loading %s", filename);
#if !defined(NONEWT)
    label = newtLabel(1, 1, "");
    newtRefresh();
    scale = newtScale(1, 3, SCALEWIDTH, (long long)len, 0);
    newtScaleSet(scale, len);
    newtRefresh();
    newtLabelSetText(label, labelbuf);
    newtScaleSet(scale, 0);
    newtRefresh();
#else                         /* NONEWT */
    printf(labelbuf);
#endif

    bytes_copied = 0;
    while ((rl = read(readfd, loadbuffer, lbsize)) > 0) {
	wl = write(writefd, loadbuffer, rl);
	bytes_copied += wl;
	if (wl != rl) {
	    newtWinMessage("Error", "OK", "Copy %s failed on %ld byte: %s",
			   filename, (long)bytes_copied, strerror(errno));
	    /* If the input file is bigger than the provided size, bytes_copied
	       might equal to the provided size because the ramdisk space is
	       limited to the provided size. So bytes_copied has to be set to
	       -1. */
	    bytes_copied = -1;
	    goto  err_ret1;
	}
#if !defined(NONEWT)
	newtScaleSet(scale, bytes_copied);
	newtRefresh();
#else                         /* NONEWT */
        printf(".");
#endif
    }
    fsync(writefd);
    if (bytes_copied != len)
	newtWinMessage("Error", "OK", "Copy %ld bytes when %ld bytes required.",
		       (long)bytes_copied, (long)len);
 err_ret1:
#if !defined(NONEWT)
    newtPopWindow();
#else                         /* NONEWT */
    printf("\n");
#endif
    free(loadbuffer);
    return  bytes_copied;
}

#if !defined(NONEWT)
newtComponent newtExtForm(newtComponent vertBar, void * helpTag, int flags) {
    newtComponent  form;

    form = newtForm(vertBar, helpTag, flags);
    newtFormAddHotKey(form, NEWT_KEY_F7);
    newtFormAddHotKey(form, NEWT_KEY_F8);
    newtFormAddHotKey(form, NEWT_KEY_F9);
    return  form;
}

void newtWinStatus(int width, int height, char * title, char * text, ...) {
    newtComponent t, f;
    char * buf = NULL;
    int size = 0;
    int i = 0;
    va_list args;

    va_start(args, text);

    do {
	size += 1000;
	if (buf) free(buf);
	buf = malloc(size);
	i = vsnprintf(buf, size, text, args);
    } while (i == size);

    va_end(args);

    newtCenteredWindow(width, height, title);

    t = newtTextbox(2, 1, width - 2, height - 2, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(t, buf);
    f = newtForm(NULL, NULL, 0);

    free(buf);

    newtFormAddComponent(f, t);

    newtDrawForm(f);
    newtRefresh();
    newtFormDestroy(f);
}

static int show_filesystem(const char *file, int flow_link);
newtComponent  newtExtRunForm(newtComponent form) {
    struct newtExitStruct es;
    for (;;) {
	newtFormRun(form, &es);
	if (es.reason == NEWT_EXIT_HOTKEY) {
	    if (es.u.key == NEWT_KEY_F7)
		show_filesystem("/", 0);
	    else if (es.u.key == NEWT_KEY_F8)
		show_mblog();
            else if (es.u.key == NEWT_KEY_F9)
                open_shell();
	} else if (es.reason == NEWT_EXIT_COMPONENT)
	    return  es.u.co;
    }
}

#define  SHOW_WIDTH   70
static const char *tflist[] = {
    "/etc/fstab",
    "/etc/inittab",
    "/proc/apm",
    "/proc/cmdline",
    "/proc/cpuinfo",
    "/proc/devices",
    "/proc/dma",
    "/proc/execdomains",
    "/proc/fb",
    "/proc/filesystems",
    "/proc/interrupts",
    "/proc/iomem",
    "/proc/ioports",
    "/proc/loadavg",
    "/proc/locks",
    "/proc/mdstat",
    "/proc/meminfo",
    "/proc/misc",
    "/proc/modules",
    "/proc/mounts",
    "/proc/mtrr",
    "/proc/partitions",
    "/proc/pci",
    "/proc/slabinfo",
    "/proc/stat",
    "/proc/swaps",
    "/proc/uptime",
    "/proc/version",
    "/usr/share/hwdata/pcitable",
    "/var/log/magicbooter.log",
    "/var/log/mount.log"
};
static void show_dir(newtComponent listbox, const char *dir);
static void show_symlink(newtComponent listbox, const char *file);
static void show_text(newtComponent listbox, const char *file);
static void show_bin(newtComponent listbox, const char *file);
static int show_filesystem(const char *file, int flow_link) {
    int            r;
    newtGrid       grid, btngrid;
    newtComponent  form, listbox, backbtn, closebtn;
    struct newtExitStruct es;
    struct stat    sbuf;
    int            i, numitem;
    char         * newfn;
    void         * retdata;

    if (flow_link)  r = stat(file, &sbuf);
    else  r = lstat(file, &sbuf);
    if (r) {
	newtWinMessage("Error", "OK",
		       "Stat %s failed: %s", file, strerror(errno));
	return 0;
    }
    if (!S_ISDIR(sbuf.st_mode) &&
	!S_ISREG(sbuf.st_mode) &&
	!S_ISLNK(sbuf.st_mode)) {
	newtWinMessage("Error", "OK",
		       "Only directory, regular file and symbolic link can be shown");
	return 0;
    }

    grid = newtCreateGrid(1, 2);
    listbox = newtListbox(-1, -1, 16,
			  NEWT_FLAG_RETURNEXIT | NEWT_FLAG_BORDER | NEWT_FLAG_SCROLL);
    newtListboxSetWidth(listbox, SHOW_WIDTH + 4);
    newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, listbox, 1, 1, 1, 0, 0, 0);

    btngrid = newtCreateGrid(2, 1);
    backbtn = newtButton(-1, -1, "Back");
    newtGridSetField(btngrid, 0, 0, NEWT_GRID_COMPONENT, backbtn, 0, 0, 0, 0, 0, 0);
    closebtn = newtButton(-1, -1, "Close");
    newtGridSetField(btngrid, 1, 0, NEWT_GRID_COMPONENT, closebtn, 0, 0, 0, 0, 0, 0);

    newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, btngrid, 1, 1, 1, 1, 0, 0);

    form = newtForm(NULL, NULL, 0);
    newtGridAddComponentsToForm(grid, form, 1);

    newtGridWrappedWindow(grid, (char *)file);
    newtGridFree(grid, 1);

    if (S_ISDIR(sbuf.st_mode))  show_dir(listbox, file);
    else if (S_ISLNK(sbuf.st_mode))  show_symlink(listbox, file);
    else {
	for (i = 0; i < sizeof(tflist) / sizeof(tflist[0]); ++i)
	    if (!strcmp(file, tflist[i]))  break;
	if (i < sizeof(tflist) / sizeof(tflist[0]))
	    show_text(listbox, file);
	else
	    show_bin(listbox, file);
    }

    r = 1;
    for (;;) {
	newtFormRun(form, &es);
	if (es.reason != NEWT_EXIT_COMPONENT)  continue;
	if (es.u.co == backbtn) { r = 0; break; }
	else if (es.u.co == closebtn)  break;
	else if (es.u.co == listbox) {
	    newfn = newtListboxGetCurrent(listbox);
	    if (newfn != NULL)
		if (show_filesystem(newfn, S_ISLNK(sbuf.st_mode)))  break;
	}
    }

    numitem = newtListboxItemCount(listbox);
    for (i = 0; i < numitem; ++i) {
	newtListboxGetEntry(listbox, numitem, NULL, &retdata);
	if (retdata != NULL)  free(retdata);
    }

    newtFormDestroy(form);
    newtPopWindow();
    return  r;
}

static void show_file(char *linebuf, const char *filename) {
    struct stat st;

    if (lstat(filename, &st)) {
	sprintf(linebuf, "Can't stat %s\n", filename);
	return;
    }
    linebuf += sprintf(linebuf, "%c",
		       S_ISLNK(st.st_mode) ? 'l':
		       S_ISSOCK(st.st_mode) ? 's':
		       S_ISFIFO(st.st_mode) ? 'f':
		       S_ISBLK(st.st_mode) ? 'b':
		       S_ISCHR(st.st_mode) ? 'c':
		       S_ISDIR(st.st_mode) ? 'd': '-');
    linebuf += sprintf(linebuf, "%c%c%c%c%c%c%c%c%c:%s%s%s%4u%6u.%6u",
		       (st.st_mode & S_IRUSR) ? 'r' : '-',
		       (st.st_mode & S_IWUSR) ? 'w' : '-',
		       (st.st_mode & S_IXUSR) ? 'x' : '-',
		       (st.st_mode & S_IRGRP) ? 'r' : '-',
		       (st.st_mode & S_IWGRP) ? 'w' : '-',
		       (st.st_mode & S_IXGRP) ? 'x' : '-',
		       (st.st_mode & S_IROTH) ? 'r' : '-',
		       (st.st_mode & S_IWOTH) ? 'w' : '-',
		       (st.st_mode & S_IXOTH) ? 'x' : '-',
		       (st.st_mode & S_ISUID) ? "su" : "--",
		       (st.st_mode & S_ISGID) ? "sg" : "--",
		       (st.st_mode & S_ISVTX) ? "st" : "--",
		       st.st_nlink,
		       st.st_uid, st.st_gid);
    if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
	linebuf += sprintf(linebuf, "   %3u,%3u",
			   major(st.st_rdev), minor(st.st_rdev));
    else
	linebuf += sprintf(linebuf, "%10lu", st.st_size);
    linebuf += sprintf(linebuf, " %s", basename(strdupa(filename)));
}

static int  decmp(const void *de1, const void *de2) {
    return  strcmp(((struct dirent *)de1)->d_name, ((struct dirent *)de2)->d_name);
}
static void show_dir(newtComponent listbox, const char *dir) {
    DIR    * d;
    struct dirent *de, *dearr, *curde;
    int      num_de;
    char     fnbuf[256];
    char     itembuf[256];

    if ((d = opendir(dir)) == NULL) {
	snprintf(itembuf, sizeof(itembuf), "Can't opendir %s", dir);
	newtListboxAppendEntry(listbox, itembuf, NULL);
	return;
    }
    num_de = 0;
    while ((de = readdir(d)) != NULL)  ++num_de;
    rewinddir(d);
    dearr = alloca(sizeof(struct dirent) * num_de);
    curde = dearr;
    while ((de = readdir(d)) != NULL) {
	if (!strcmp(de->d_name, "."))  continue;
	if (!strcmp(de->d_name, ".."))  continue;
	memcpy(curde, de, sizeof(struct dirent));
	++curde;
    }
    closedir(d);
    num_de = curde - dearr;
    if (num_de > 0) {
	qsort(dearr, num_de, sizeof(struct dirent), decmp);
	for (curde = dearr; curde - dearr < num_de; ++curde) {
	    if (!strcmp(dir, "/"))
		snprintf(fnbuf, sizeof(fnbuf), "/%s", curde->d_name);
	    else
		snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dir, curde->d_name);
	    show_file(itembuf, fnbuf);
	    newtListboxAppendEntry(listbox, itembuf, strdup(fnbuf));
	}
    }
}

static void show_symlink(newtComponent listbox, const char *file) {
    char  itembuf[256];
    int   lenprefix, lenlink;

    snprintf(itembuf, sizeof(itembuf), "symbolic link:");
    newtListboxAppendEntry(listbox, itembuf, NULL);
    strcpy(itembuf, "-> :");  lenprefix = strlen(itembuf);
    lenlink = readlink(file, itembuf + lenprefix,
		       sizeof(itembuf) - lenprefix);
    if (lenlink < 0) {
	snprintf(itembuf, sizeof(itembuf),
		 "Readlink failed: %s", strerror(errno));
	newtListboxAppendEntry(listbox, itembuf, NULL);
    } else {
	itembuf[lenprefix + lenlink] = 0;
	newtListboxAppendEntry(listbox, itembuf, strdup(file));
    }
}

static void show_text(newtComponent listbox, const char *file) {
    FILE *fp;
    char  inbuf[256], *cur_in;
    char  showbuf[257], *cur_show;
    if ((fp = fopen(file, "r")) == NULL) {
	snprintf(showbuf, sizeof(showbuf), "Can't open %s", file);
	newtListboxAppendEntry(listbox, showbuf, NULL);
	return;
    }
    while (fgets(inbuf, sizeof(inbuf), fp) != NULL) {
	cur_show = showbuf;
	for (cur_in = inbuf;
	     cur_show - showbuf < sizeof(showbuf) - 1 &&
		 *cur_in != '\n' && *cur_in != 0;
	     ++cur_in) {
	    if (*cur_in != '\t')  *cur_show++ = *cur_in;
	    else
		while (cur_show - showbuf < sizeof(showbuf) - 1) {
		    *cur_show++ = ' ';
		    if ((cur_show - showbuf) % 8 == 0)  break;
		}
	}
	*cur_show++ = 0;
	newtListboxAppendEntry(listbox, showbuf, NULL);
    }
    fclose(fp);
}
static const int limit = 1024;
static void show_bin(newtComponent listbox, const char *file) {
    int           ifd;
    unsigned char buffer[14];
    unsigned      address;
    int           i, rn;
    char          itembuf[256], *cur;

    if ((ifd = open(file, O_RDONLY)) < 0) {
	snprintf(itembuf, sizeof(itembuf), "Can't open %s\n", file);
	newtListboxAppendEntry(listbox, itembuf, NULL);
	return;
    }
    address = 0;
    while ((rn = read(ifd, buffer, sizeof(buffer))) > 0) {
	cur = itembuf + sprintf(itembuf, "%08x|", address);
	for (i = 0; i < sizeof(buffer); ++i)
	    if (i < rn)
		cur += sprintf(cur,
			       "%02x%c", buffer[i], (i < rn - 1) ? ' ' : '|');
	    else
		cur += sprintf(cur, "  %c", (i < rn - 1) ? ' ' : '|');
	for (i = 0; i < sizeof(buffer); ++i)
	    cur += sprintf(cur, "%c", (i >= rn) ? ' ':
			   (buffer[i] >= ' ' && buffer[i] < 127) ? buffer[i] : '.');
	cur += sprintf(cur, "|");
	newtListboxAppendEntry(listbox, itembuf, NULL);
	address += rn;
	if (limit > 0 && address > limit)  break;
    }
    close(ifd);
}
#endif /* !NONEWT */


/* Write some wrapper functions for newt in no newt mode */
#ifdef NONEWT
char read_key(const char *prompt, char key_min, char key_max, char key_default)
{
    int c = -1, last_c, not_cr;

    for(;;) {
	printf("%s (%c-%c)[%c]: ", prompt, key_min, key_max, key_default);
	c = getchar();
	not_cr = 0;
	while (c != '\n') {
	    not_cr++;
	    last_c = c;
	    c = getchar();
	}
	if (not_cr == 0)
	    return key_default;
	else if (not_cr != 1 || !(last_c >= key_min && last_c <= key_max))
	    printf("Input error!\n");
	else
	    return last_c;
    }
}

void wait_enter_key(void)
{
    int c;

    printf("Press <Enter> to continue.\n");
    do {
	c = getchar();
    } while (c != '\n');
}

int newtCenteredWindow(int width, int height, const char * title)
{
    printf("\n%s\n", title);
    return 0;
}


void newtWinMessage(char * title, char * buttonText, char * text, ...)
{
    printf("\n*** %s ***\n", title);
    printf("%s\n", text);
    printf("  <%s>\n", buttonText);
    wait_enter_key();
}

int newtWinChoice(char * title, char * button1, char * button2,
                  char * text, ...)
{
    printf("\n*** %s ***\n", title);
    printf("%s\n", text);
    printf("  <1> %s\n", button1);
    printf("  <2> %s\n", button2);
    /* 1 - button1, 2 - button2, 0 - cancel (not used here)*/
    return read_key("Please choose", '1', '2', '1') - '0';
}
#endif  /* NONEWT */
